package com.stone.framework.batch.tasklet;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

/**
 * 文件信息统计 tasklet<br>
 * 支持文件名称{@link Type#NAME}，文件大小{@link Type#SIZE}，文件行数{@link Type#LINES}，文件生成时间{@link Type#TIME}，自定义{@link Type#customize(String)}
 * 
 * @author ybliu
 * @date 2020/07/27
 */
public class FileCountingTasklet implements Tasklet, InitializingBean {

    private Resource source;
    private Resource target;
    private Type[] array;
    private String delimiter;
    private Charset charset = StandardCharsets.UTF_8;

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        if (!source.exists()) {
            throw new FileNotFoundException(source.getFilename());
        }
        String result = Stream.of(this.array).map(type -> type.getStatistics(this.source, this.charset))
            .collect(Collectors.joining(this.delimiter));

        Path path = this.target.getFile().toPath();
        Files.deleteIfExists(path);
        Files.createFile(path);
        Files.write(path, Arrays.asList(result), this.charset, StandardOpenOption.WRITE, StandardOpenOption.APPEND);

        contribution.setExitStatus(ExitStatus.COMPLETED);
        return RepeatStatus.FINISHED;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.source, "source must not be null");
        Assert.notNull(this.target, "target must not be null");
        Assert.notNull(this.array, "array must not be null");
        Assert.notNull(this.delimiter, "delimiter must not be null");
    }

    public void setSource(Resource source) {
        this.source = source;
    }

    public void setTarget(Resource target) {
        this.target = target;
    }

    public void setArray(Type[] array) {
        this.array = array;
    }

    public void setDelimiter(String delimiter) {
        this.delimiter = delimiter;
    }

    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    public static class Type {
        /**
         * 文件名称
         */
        public static final Type NAME = new Type((resource, charset) -> resource.getFilename());

        /**
         * 文件大小
         */
        public static final Type SIZE = new Type((resource, charset) -> {
            try {
                return String.valueOf(resource.contentLength());
            } catch (IOException e) {
                return "ERROR";
            }
        });

        /**
         * 行数
         */
        public static final Type LINES = new Type((resource, charset) -> {
            try {
                long lines = Files.lines(Paths.get(resource.getURI()), charset).count();
                return String.valueOf(lines);
            } catch (IOException e) {
                return "ERROR";
            }
        });

        /**
         * 时间（其他格式时间使用 {@link #customize(String)} 自定义实现）
         */
        public static final Type TIME = new Type((resource, charset) -> {
            LocalDateTime time = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
            return time.format(formatter);
        });

        /**
         * 自定义内容
         * 
         * @param content
         * @return
         */
        public static final Type customize(String content) {
            return new Type((resource, charset) -> content);
        }

        private BiFunction<Resource, Charset, String> function;

        private Type(BiFunction<Resource, Charset, String> function) {
            this.function = function;
        }

        public String getStatistics(Resource source, Charset charset) {
            return function.apply(source, charset);
        }
    }

}
